﻿using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Windows;
using System.Windows.Forms;
using System.Windows.Input;
using MLSharp;
using MLSharp.Clustering;
using MLSharp.Filters;
using MLSharp.Weka;
using MLSharp.Utilities;

namespace KnowledgeFrontierMiner
{
	/// <summary>
	/// Interaction logic for MainWindow.xaml
	/// </summary>
	public partial class MainWindow : Window
	{
		/// <summary>
		/// The currently-loaded dataset.
		/// </summary>
		private IDataSet mDataSet;

		/// <summary>
		/// Stores the most-recent run of KFD.
		/// </summary>
		private CobwebKnowledgeFrontierDiscovery mKfd;

		/// <summary>
		/// Initializes the class.
		/// </summary>
		public MainWindow()
		{
			//Do not remove this!
			InitializeComponent();
		}

		/// <summary>
		/// Runs KFD.
		/// </summary>
		//TODO: This should really be run in a background thread so that the UI remains responsive.
		private void RunKnowledgeFrontierDiscovery()
		{
			//Prompt user for settings.
			KfdSettings settings = new KfdSettings(mDataSet.Attributes.Select(a => a.Name).ToArray());
			settings.ShowDialog();

			//Begin timing the process.
			Stopwatch stopwatch = Stopwatch.StartNew();

			CobwebKnowledgeFrontierDiscovery kfd = new CobwebKnowledgeFrontierDiscovery();

			//Only apply the settings if the user chose to specify settings.
			if (settings.DialogResult.HasValue && settings.DialogResult.Value)
			{
				kfd.Acuity = settings.Acuity ?? kfd.Acuity;
				kfd.Alpha = settings.Alpha ?? kfd.Alpha;
				kfd.Cutoff = settings.Cutoff ?? kfd.Cutoff;

				if (settings.AttributesToFilter != null && settings.AttributesToFilter.Length > 0)
				{
					new AttributeExcluderFilter(settings.AttributesToFilter).Transform(mDataSet);
				}
			}

			//Search for frontiers
			kfd.DiscoverKnowledgeFrontiers(mDataSet);

			stopwatch.Stop();

			KnowledgeFrontierTreeView.DataContext = null;
			KnowledgeFrontierTreeView.ItemsSource = null;
			KnowledgeFrontierTreeView.Items.Clear();

			StatusTextBox.Text = string.Format("Found {0} clusters and {1} frontiers in {2} seconds.",
			                                   kfd.Cobweb.NumberOfClusters(),
			                                   kfd.KnowledgeFrontiers.Length, stopwatch.Elapsed.TotalSeconds);

			KnowledgeFrontierTreeView.DataContext = KnowledgeFrontierTreeView.ItemsSource = new[] { GetKnowledgeFrontierViewData(kfd) };

			//Store a reference to KFD so that reports can be performed.
			mKfd = kfd;
		}

		/// <summary>
		/// Gets a wrapper that combines <see cref="IClusterNode"/>s with 
		/// <see cref="IKnowledgeFrontier"/> information.
		/// </summary>
		/// <param name="kfd"></param>
		/// <returns></returns>
		private KnowledgeFrontierView GetKnowledgeFrontierViewData(CobwebKnowledgeFrontierDiscovery kfd)
		{
			//Grab the index of the accuracy; we'll need that later.
			int accuracyIndex = kfd.Cobweb.DataSet.Attributes.FindIndex(a => a.Name == "Accuracy");

			//Create a look-up for knowledge frontier's by IDs.  
			var frontierLookup = kfd.KnowledgeFrontiers.ToDictionary(f => f.Node.NodeId, f => f);

			//Create the root view.
			KnowledgeFrontierView root = new KnowledgeFrontierView(kfd.RootNode, accuracyIndex, mDataSet);

			//If the root is a knowledge frontier, mark it.
			if (frontierLookup.ContainsKey(root.NodeId))
			{
				root.KnowledgeFrontier = frontierLookup[root.NodeId];
			}

			//Populate the children recursively.
			foreach (IClusterNode child in kfd.RootNode.Children)
			{
				BuildKnowledgeFrontierViewData(child, root, frontierLookup, accuracyIndex);
			}

			return root;
		}

		/// <summary>
		/// Recursively populates a tree of <see cref="KnowledgeFrontierView"/> objects.
		/// </summary>
		/// <param name="node"></param>
		/// <param name="parent"></param>
		/// <param name="frontierLookup"></param>
		/// <param name="accuracyIndex">The index of the accuracy attribute.</param>
		private void BuildKnowledgeFrontierViewData(IClusterNode node, KnowledgeFrontierView parent, Dictionary<int, IKnowledgeFrontier> frontierLookup, int accuracyIndex)
		{
			KnowledgeFrontierView current = new KnowledgeFrontierView(node, accuracyIndex, mDataSet);

			parent.Children.Add(current);

			if (frontierLookup.ContainsKey(current.NodeId))
			{
				current.KnowledgeFrontier = frontierLookup[current.NodeId];
			}

			foreach (IClusterNode child in node.Children)
			{
				BuildKnowledgeFrontierViewData(child, current, frontierLookup, accuracyIndex);
			}
		}

		/// <summary>
		/// Fired when the Open command is executed.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void OpenCommand_Executed(object sender, ExecutedRoutedEventArgs e)
		{
			using (OpenFileDialog dialog = new OpenFileDialog())
			{
				dialog.CheckFileExists = true;
				dialog.Filter = "Supported Files (*.xls,*.arff)|*.xls;*.arff|Excel files (*.xls)|*.xls|Weka ARFF Files (*.arff)|*.arff|All Files (*.*)|*.*";
				dialog.FilterIndex = 1;

				switch (dialog.ShowDialog())
				{
					case System.Windows.Forms.DialogResult.OK:
						mDataSet = DataSetLoader.Load(dialog.FileName);

						RunKnowledgeFrontierDiscovery();

						//Remove any prevoius filenames
						if (Title.IndexOf("-") >= 0)
						{
							//We're appending " - ..." to the window title, so remove the dash
							//and the space before it.
							Title = Title.Substring(0, Title.IndexOf("-")-1);
						}

						//Append the filename to the title.
						this.Title = this.Title + " - " + Path.GetFileName(dialog.FileName);

						break;
					default:
						break;
				}
			}
		}

		/// <summary>
		/// Always returns true (for now).
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void OpenCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e)
		{
			e.CanExecute = true;
		}

		/// <summary>
		/// Exits the application.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void ExitCommand_Executed(object sender, ExecutedRoutedEventArgs e)
		{
			//TODO: Prompt to save?
			Close();
		}

		/// <summary>
		/// Always returns true (for now).
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void ExitCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e)
		{
			e.CanExecute = true;
		}

		/// <summary>
		/// Fired when the Summary Report menu item is clicked; generates and displays
		/// a summary of the KFD output.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void SummaryReport_Executed(object sender, ExecutedRoutedEventArgs e)
		{
			SummaryReport summaryReport = new SummaryReport(mKfd);
			summaryReport.Show();
		}

		/// <summary>
		/// Enables the menu when a dataset has been loaded and KFD has been applied.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void SummaryReport_CanExecute(object sender, CanExecuteRoutedEventArgs e)
		{
			e.CanExecute = mKfd != null;
		}
	}
}
